package speculoos.valid;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import speculoos.core.Mapper;
import speculoos.core.MapperCollector;

/**
 * A validator for composing a sequence of validators.
 * <p>
 * Instances of this class hold a sequence of validation rules that are matched
 * when needed against an instance of Map. Validation can work in either of two
 * modes: <em>UNIVERSAL</em> or <em>EXISTENTIAL</em>.
 * </p>
 * <p>
 * In <em>UNIVERSAL</em> mode, a map is validated if <strong>no</strong>
 * validation rule rejects it. In <em>EXISTENTIAL</em> mode, a map is
 * validated if at least <strong>one</strong> validation rule accepts it.
 * </p>
 * This class is used to create complex logic expressions for validating input
 * maps like AND and OR maps.
 * 
 * @author nono
 * @version $Id: CompositeValidator.java 231 2006-02-07 09:10:03Z
 *          /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud
 *          Bailly/emailAddress=abailly@norsys.fr $
 */
public class CompositeValidator implements Validator, MapperCollector {

	/**
	 * this constant is used if all validators must be true for this validator
	 * to be valid.
	 */
	public static final int UNIVERSAL = 0;

	/**
	 * This constant is used if at least one validator must be true for this
	 * validator to be valid.
	 */
	public static final int EXISTENTIAL = 1;

	/**
	 * This constant is used if all validators should fail.
	 */
	public static final int NEGATIVE = 2;

	/*
	 * the list of compound validators
	 */
	private ArrayList validators;

	/*
	 * the logic of this composite
	 */
	private int includeExclude;

	/**
	 * Create a validation logic with given characteristic.
	 * 
	 * @param logic
	 *            one of {@link #EXISTENTIAL} or {@link #UNIVERSAL}
	 */
	public CompositeValidator(int logic) {
		this.includeExclude = logic;
	}

	/**
	 * Create acomposite validator with default logic (UNIVERSAL)
	 */
	public CompositeValidator() {
		this.includeExclude = UNIVERSAL;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.Mapper#map(java.lang.Object, java.util.Map)
	 */
	public Object map(Object input, Map env) throws InvalidateException {
		/* include/exclude logic */
		if (includeExclude == CompositeValidator.UNIVERSAL) {
			if (validators == null)
				return input;
			for (Iterator i = validators.iterator(); i.hasNext();) {
				Validator valid = (Validator) i.next();
				input = valid.map(input, null);
			}
			return input;
		} else if (includeExclude == CompositeValidator.EXISTENTIAL) {
			if (validators == null)
				throw new InvalidateException(getName()
						+ ": No validator defined in existential composite");
			for (Iterator i = validators.iterator(); i.hasNext();) {
				Validator valid = (Validator) i.next();
				try {
					input = valid.map(input, null);
					return input;
				} catch (InvalidateException e) {
					// NOP
				}
			}
			throw new InvalidateException(getName()
					+ ": No validator matched in existential composite");
		} else if (includeExclude == CompositeValidator.NEGATIVE) {
			if (validators == null)
				throw new InvalidateException(getName()
						+ ": No validator defined in negative composite");
			boolean failed = false;
			for (Iterator i = validators.iterator(); i.hasNext();) {
				Validator valid = (Validator) i.next();
				try {
					input = valid.map(input, null);
					failed = true;
				} catch (InvalidateException e) {
					// NOP
				}
				if (failed)
					throw new InvalidateException(getName() + ": Validator "
							+ valid.getName()
							+ " succeeded in negative composite");
			}
			return input;
		} else
			throw new IllegalStateException(
					"Invalid setting for include/exclude logic");
	}

	/**
	 * @return Returns the includeExclude.
	 */
	public int getIncludeExclude() {
		return includeExclude;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#addMapper(speculoos.core.Mapper)
	 */public void addMapper(Mapper v) {
		if (validators == null)
			validators = new ArrayList();
		validators.add(v);
	}

	/**
	 * @param includeExclude
	 *            The includeExclude to set.
	 */
	public void setIncludeExclude(int includeExclude) {
		this.includeExclude = includeExclude;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#addMappers(java.util.Collection)
	 */public void addMappers(Collection col) {
		if (validators == null)
			validators = new ArrayList();
		validators.addAll(col);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#getMappers()
	 */
	public Collection getMappers() {
		return validators;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.Mapper#getName()
	 */
	public String getName() {
		switch (includeExclude) {
		case UNIVERSAL:
			return "UniversalValidator";
		case EXISTENTIAL:
			return "ExistentialValidator";
		case NEGATIVE:
			return "NegativeValidator";
		default:
			return "CompositeValidator";

		}
	}

}
